const Path = require("path");
const { File } = require("ada-util");
const SourceMap = require("./sourcemap");
const Config = require('./../config/index');
const Locker = require("../locker");

class Packer {
    constructor(sourcePath, option = {}, hooker) {
        this._sourcePath = sourcePath;
        this._config = this._initConfig(sourcePath, option);
        this._config.hooker = hooker;
        this._config.hook(hooker);
        this._sourceMap = new SourceMap(this._config);
        this._locker = new Locker(this.config.distPath);
        hooker.setContext({ sourceMap: this._sourceMap });
    }

    get name() {
        return this._config.name;
    }

    get version() {
        return this._config.version;
    }

    get config() {
        return this._config;
    }

    get sourcePath() {
        return this.config.sourcePath;
    }

    get siteURL() {
        return this.config.siteURL;
    }

    get sourceMap() {
        return this._sourceMap;
    }

    get locker() {
        return this._locker;
    }

    _initConfig(sourcePath, option = {}) {
        let config = Config.getPackerConfig(option);
        const basePath = Path.resolve(sourcePath, config.basePath).replace(/\\/g, "/");
        const pkgInfo = require(Path.resolve(sourcePath, './package.json'));
        if (!Array.isArray(config.entryPath)) {
            config.entryPath = [config.entryPath];
        }
        config.entryPath = config.entryPath.map(path => {
            return Path.resolve(basePath, path).replace(/\\/g, "/");
        });
        config.appVersion = pkgInfo.version;
        config.appName = pkgInfo.name;
        config.version = 'manifest';
        config.basePath = basePath;
        config.projectPath = sourcePath;
        config.siteURL = "/" + config.name + "/";
        config.nmodulePath = Path.join(sourcePath, "./node_modules/").replace(/\\/g, "/");
        config.distPath = Path.resolve(basePath, config.distPath, `./${config.name}`).replace(/\\/g, "/");
        config.hookerPath = Path.resolve(basePath, config.hookerPath).replace(/\\/g, "/");
        config.sourcePath = Path.join(basePath, config.sourcePath).replace(/\\/g, "/");
        // config.entryPath = Path.join(basePath, config.entryPath).replace(/\\/g, "/");
        config.mainEntryPath = Path.join(basePath, config.main).replace(/\\/g, "/");
        // if (config.hooker) {
        //     config.hookerPath = Path.join(basePath, config.hooker).replace(/\\/g, "/");
        // }
        if (config.worker && config.worker.path) {
            config.workerPath = Path.join(basePath, config.worker.path).replace(/\\/g, "/");
        }
        if (config.staticPath) {
            if (!Array.isArray(config.staticPath)) {
                config.staticPath = [config.staticPath];
            }
            config.staticPath = config.staticPath.map(a => {
                return Path.join(basePath, a).replace(/\\/g, "/");
            });
        }
        if (new File(config.hookerPath).exist) {
            config.appHooker = config.hookerPath.substring(config.sourcePath.length);
        } else {
            config.appHooker = "";
        }
        config.cachePath = Path.resolve(config.workspace, './.cache/frontend');
        return config;
    }

    _getPackOutputInfo() {
        return {
            name: this.name,
            url: this.siteURL,
            version: this.config.version,
            appVersion: this.config.appVersion,
            develop: this.config.develop,
            hooker: this.config.appHooker,
            codeMap: this.config.codeMap
        };
    }

    getCurrentState(type, files = []) {
        type = this.sourceMap.outputer.rebuild ? "reload" : type;
        if (type === 'reload') {
            this.sourceMap.outputer.rebuild = false;
        }
        if (files.includes(this.config.hookerPath)) {
            type = 'reload';
        }
        return {
            type,
            map: this.sourceMap.outputer.getSourceMap(),
            log: this.sourceMap.outputer.getLogInfo(),
            name: this.sourceMap.config.name,
            version: this.config.version,
            hooker: this.config.appHooker,
            files: this.getChangedModule(files),
            app: this._config.name
        };
    }

    checkChanges(type, files) {
        return this.sourceMap.checkChanges(type, files);
    }

    repackByFilesChange(type, files) {
        files = files.map(file => file.replace(/\\/g, "/"));
        files = this.getFilesInWatch(files);
        if (files && files.length > 0) {
            return this.sourceMap[`${type}Files`](files).then(files => {
                return this.locker.set('pack', this._getPackOutputInfo()).save().then(() => files);
            }).then(files => {
                if (Array.isArray(files)) {
                    return this.getCurrentState('edit', files);
                } else {
                    return this.getCurrentState('edit');
                }
            });
        } else {
            return Promise.resolve();
        }
    }

    getChangedModule(files) {
        return files.map(file => {
            let key = Reflect.ownKeys(this.sourceMap._map).find(a => this.sourceMap._map[a].path === file);
            if (key) {
                return this.sourceMap._map[key].info.required;
            }
        }).filter(a => a !== undefined);
    }

    getFilesInWatch(files) {
        let watchPaths = this.getWatchPaths();
        return files.filter(a => {
            return watchPaths.find(b => a.indexOf(b) === 0) !== undefined;
        });
    }

    getWatchPaths() {
        if (this.config.watchNodeModules) {
            let ignore = ['adajs', 'ada-pack'];
            let info = require(Path.resolve(this.config.nmodulePath, './../package.json')).dependencies || {};
            let f = Reflect.ownKeys(info).filter(a => {
                return !ignore.includes(a);
            }).map(a => {
                return Path.resolve(this.config.nmodulePath, `./${a}`).replace(/\\/g, "/");
            });
            return [this.config.sourcePath, ...f];
        } else {
            return [this.config.sourcePath];
        }
    }

    getPackOutputInfo() {
        return this.locker.get('pack');
    }

    pack() {
        let config = this.config, file = new File(config.distPath);
        if (!config.develop) {
            return config.hooker.emit("before-pack", { config }).then(() => {
                if (file.exist) {
                    return file.empty();
                } else {
                    file.make();
                }
            }).then(() => {
                return this.sourceMap.map();
            }).then(() => {
                return this.locker.set('pack', this._getPackOutputInfo()).save();
            }).then(() => {
                return config.hooker.emit("after-pack", { config });
            });
        } else {
            if (!file.exist) {
                file.make();
            }
            return config.hooker.emit("before-pack", { config }).then(() => {
                return this.sourceMap.map();
            }).then(() => {
                return this.locker.set('pack', this._getPackOutputInfo()).save();
            }).then(() => {
                return config.hooker.emit("after-pack", { config });
            });
        }
    }
}

module.exports = Packer;